package com.openatc.agent.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.openatc.agent.controller.MessageController;
import com.openatc.agent.model.DictConfig;
import com.openatc.agent.model.THisParams;
import com.openatc.agent.service.*;
import com.openatc.agent.utils.HttpUtil;
import com.openatc.agent.utils.TokenUtil;
import com.openatc.comm.common.CommClient;
import com.openatc.comm.common.CosntDataDefine;
import com.openatc.comm.data.MessageData;
import com.openatc.comm.handler.IMsgPostHandler;
import com.openatc.comm.handler.IMsgPreHandler;
import com.openatc.core.model.InnerError;
import com.openatc.core.model.RESTRet;
import com.openatc.core.util.RESTRetUtils;
import com.openatc.model.model.AscsBaseModel;
import com.openatc.model.model.DevCover;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import static com.openatc.comm.common.CommunicationType.COMM_SERVER_TYPE_CENTER;
import static com.openatc.comm.common.CosntDataDefine.*;
import static com.openatc.comm.common.LogUtil.CreateSuccessResponceData;
import static com.openatc.core.common.IErrorEnumImplOuter.*;

/**
 * @Classname MessageServiceImpl
 * @Description
 * @Date 2022/3/28 14:02
 * @Created by panleilei
 */
@Service
public class MessageServiceImpl implements MessageService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private Gson gson = new Gson();

    private final static Logger logger = Logger.getLogger(MessageController.class.toString());

    @Autowired
    private IMsgPostHandler msgPostHandler;

    @Autowired
    private IMsgPreHandler msgPreHandler;
    @Autowired
    private DictConfigRepository dictConfigRepository;
    protected CommClient commClient = new CommClient();

    @Autowired(required = false)
    protected HisParamServiceImpl hisParamService;

    @Autowired
    private AscsDao mDao;

    @Autowired
    private FeatureService featureService;

    @PostConstruct
    public void init(){
        // 设置通讯模式为UDP固定端口
        commClient.setCommunicationServerType(COMM_SERVER_TYPE_CENTER);
    }

    @Override
    public RESTRet getStatusPatternFromRedis(MessageData requestData) {
        String agentid = requestData.getAgentid();
        String key = "response:status/pattern:" + agentid;
        String value = stringRedisTemplate.opsForValue().get(key);
        if (value == null){
//            InnerError innerError = RESTRetUtils.innerErrorObj(agentid, IErrorEnumImplInner.E_109, requestData);
//            return RESTRetUtils.errorDetialObj(IErrorEnumImplOuter.E_4001,innerError);
            // 不应答4109错误，避免界面一直弹错误消息
            return RESTRetUtils.successObj(null);
        }
        MessageData responseData = gson.fromJson(value,MessageData.class);
        return RESTRetUtils.successObj(responseData);
    }

    @Override
    public void saveStatusPatternToRedis(MessageData responseData) {
        String key = "response:status/pattern:" + responseData.getAgentid();
        int expire = 720;
        String value = gson.toJson(responseData);
        stringRedisTemplate.opsForValue().set(key,value,expire, TimeUnit.MILLISECONDS);
    }


    /**
     * 封装向信号机发送消息的接口，供其他地方调用，避免在controller中引入MessageController
     * @param requestData
     * @return
     */
    public RESTRet postDevsMessage(MessageData requestData) {
        // 每次向信号机发送消息，都要查一次数据库，是否可以缓存设备的在线状态，提升效率 todo
        AscsBaseModel ascsBaseModel = mDao.getAscsByID(requestData.getAgentid());
        return postDevsMessageByAscsBaseModel(ascsBaseModel,requestData);
    }

    /**
     *
     * @param ascsBaseModel
     * @param requestData
     * @return
     */
    public RESTRet postDevsMessage(AscsBaseModel ascsBaseModel,MessageData requestData) {
        return postDevsMessageByAscsBaseModel(ascsBaseModel,requestData);
    }


    /**
     * @param requestData 发送给设备的请求消息
     * @return RESTRet
     * @Title: postDevsMessageByAscsBaseModel
     * @Description: 发送设备消息
     */
    private RESTRet postDevsMessageByAscsBaseModel(AscsBaseModel ascsBaseModel, MessageData requestData) {
        if (ascsBaseModel == null) {
            logger.info("GetDevById is null, request = " + requestData.toString());
            return RESTRetUtils.errorObj(false,E_8001);
        }

        // 不在线设备的消息特殊处理
        if(!"UP".equals(ascsBaseModel.getState())){
            String infotype = requestData.getInfotype();
            // 针对方案状态消息的特殊处理，这个消息调用较多，平台上经常会包异常提示。此处不应答此消息的异常，优化平台用户体验。
            if( workstatus.equals(infotype) ){
                return RESTRetUtils.successObj(CreateSuccessResponceData(requestData.getAgentid(),requestData.getOperation(),requestData.getInfotype(),"postmsg-dev-isnot-up"));
            }
            // 上载相位时，从数据库中获取
            else if( phase.equals(infotype) ) {
                MessageData features = featureService.getFeatures(ascsBaseModel.getAgentid());
                return RESTRetUtils.successObj(features);
            }
            // 上载方案时，从数据库中获取
            else if( timepattern.equals(infotype) ) {
                MessageData features = featureService.getFeatures(ascsBaseModel.getAgentid());
                return RESTRetUtils.successObj(features);
            }
            // 上载特征参数时，从数据库中获取
            else if( allfeature.equals(infotype) ) {
                MessageData features = featureService.getFeatures(ascsBaseModel.getAgentid());
                return RESTRetUtils.successObj(features);
            }
        }

        // 消息前置处理器
        RESTRet preret = msgPreHandler.process(requestData);
        if (preret.getData() != null){
            return preret;
        }

        // 判断设备是否平台对接，如果是平台对接的设备，将平台地址填入通行IP
        String platform = ascsBaseModel.getPlatform();
        if (platform != null) {
            if(!platform.isEmpty() && !platform.equals("OpenATC")){ // 目前平台配置为OpenATC的设备，也认为是直连的
                // 配置了平台类型，找到该平台的信息，并填入设备IP中
                List<DictConfig> DictConfigList = dictConfigRepository.findByConfigtypeAndKey("platform",platform);
                // 找不到平台信息
                if(DictConfigList.size() == 0) {
                    logger.info("Cannot find DictConfig! " + platform + " requestData:" + requestData.toString());
                    return RESTRetUtils.errorObj(false,E_8005);
                }
                // 平台信息找到，但大于1个
                else if(DictConfigList.size() > 1) {
                    logger.info("Platform is not unique! " + platform + " requestData:" + requestData.toString());
                    return RESTRetUtils.errorObj(false,E_8006);

                }
                // 找到了唯一的平台信息
                else {
                    String platformIPStr = DictConfigList.get(0).getValue();
                    if (platformIPStr == null) {
                        logger.info("IP and port of platform is null!" + platform + " DictConfigList:" + DictConfigList);
                        return RESTRetUtils.errorObj(false,E_8007);
                    }
                    String[] array = platformIPStr.split("_");
                    String ip = array[0];
                    int port = Integer.parseInt(array[1]);
                    JsonObject ascsParam = ascsBaseModel.getJsonparam();
                    ascsParam.addProperty("ip",ip);
                    ascsParam.addProperty("port",port);
                }
            }
        }

        // 发送请求，并把应答返回
        RESTRet responceData = commClient.devMessage(requestData, ascsBaseModel);

        DevCover devLogin = null;
        if(responceData.isSuccess()){
            if(requestData.getInfotype().equals(login)){
                MessageData mdLogin = (MessageData) responceData.getData();
                devLogin = gson.fromJson(mdLogin.getData(), DevCover.class);
            }
        }

        // 处理应答后的请求
        RESTRet ret = msgPostHandler.process(requestData, responceData);
        // 后置处理后，应答处理完后的消息。当消息内容为空，代表无需处理，应答原始消息
        if (ret.getData() != null) {
            responceData = ret;
        }

        // 把设置请求set-request的操作保存到历史记录中，后续整合到后置处理器中 todo
        if (requestData.getOperation().equals("set-request")) {
            String userName = TokenUtil.getCurrentUserName();
            String operatorIp = HttpUtil.getIpAddress();

            CreateHisParam(requestData, responceData, operatorIp, userName);
        }


        return responceData;
    }


    /**
     * @param requestData  请求消息
     * @param res 应答消息
     * @return THisParams 操作记录
     * @Title: CreateHisParam
     * @Description: 生成一条操作记录
     */
    public THisParams CreateHisParam(MessageData requestData, RESTRet res, String ip, String userName) {
//        logger.info( "Create History Param - requestData： " + requestData + " user：" + user);

        THisParams hisParams = new THisParams();

        //操作者
        if (StringUtils.hasLength(userName)) {
            hisParams.setOperator(userName);
        } else {
            hisParams.setOperator("");
        }

        //操作时间自动生成
        //操作源地址
        hisParams.setSource(ip);
        //设备id
        hisParams.setAgentid(requestData.getAgentid());
        //消息类型
        hisParams.setInfotype(requestData.getInfotype());
        //请求内容
        try {
            hisParams.setRequestbody(requestData.getData().toString());
        } catch (Exception e) {
            hisParams.setRequestbody("{}");
        }
        if (res.isSuccess()) {
            MessageData responceData = (MessageData)res.getData();
            if(responceData != null){
                //消息描述
                String operation = responceData.getOperation();
                hisParams.setStatus(operation);
                //响应内容 todo 这里不一定有值，可能是空指针
                if(responceData.getData() != null)
                    hisParams.setResponsebody(responceData.getData().toString());
                //消息子类型
                int subInfoType = 0;
                if (operation.equals("set-response")) {
                    //控制消息，需要判断子类型 todo 这里不一定有值，可能是空指针
                    if(requestData.getInfotype().equals(CosntDataDefine.ControlPattern)) {
                        if(requestData.getData() != null) {
                            try {
                                subInfoType = requestData.getData().getAsJsonObject().get("control").getAsInt();
                            } catch (Exception e) {
                                logger.warning("control/interrupt has no control value!");
                            }
                        }
                    } else if (CosntDataDefine.allfeature.equals(requestData.getInfotype()) || CosntDataDefine.timepattern.equals(requestData.getInfotype())){
                        // 下载方案，消息子类型201
                        subInfoType = 201;
                    }
                }
                hisParams.setSubInfoType(subInfoType);
                //请求错误码
                String responseCode = res.getCode();
                hisParams.setResponseCode(responseCode);
                //特征参数错误码
                int deviceErrorCode = 0;
                if (operation.equals(erroranswer)) {
                    deviceErrorCode = responceData.getData().getAsJsonObject().get("code").getAsInt();
                }
                hisParams.setDeviceErrorCode(deviceErrorCode);
            }
        } else {
            InnerError innerError = (InnerError)res.getData();
            if(innerError != null){
                //消息描述
                String operation = requestData.getOperation();
                hisParams.setStatus(operation);
                //响应内容
                String responseBodey = JSONObject.toJSONString(innerError.getContent());
                hisParams.setResponsebody(responseBodey);
                //消息子类型
                int subInfoType = 0;
                hisParams.setSubInfoType(subInfoType);
                //请求错误码
                String responseCode = res.getCode();
                hisParams.setResponseCode(responseCode);
                //请求内部错误码
                String innerErrorCode = innerError.getErrorCode();
                hisParams.setInnerErrorCode(innerErrorCode);
                //特征参数错误码
                int deviceErrorCode = 0;
                hisParams.setDeviceErrorCode(deviceErrorCode);
            }
        }
        hisParamService.insertHisParam(hisParams);
        return hisParams;
    }

}
